// Copyright (c) 2021 Presto Labs Pte. Ltd.
// Author: daniel

#pragma once

#include <memory>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <utility>

#include "coin2/exchange/base/order/symbol_cache.h"
#include "coin2/exchange/base/symbology/currency.h"

namespace coin2::exchange::base::order {

class IProductInfoCache {
 public:
  using ICurrency = coin2::exchange::base::symbology::ICurrency;

 public:
  virtual ~IProductInfoCache() {}
  virtual const IProduct* GetProductByNative(const std::string& symbol_str, int64_t timestamp) = 0;
  virtual const IProduct* GetProductByNorm(const std::string& symbol_str, int64_t timestamp) = 0;
  virtual const ICurrency* GetCurrencyByNative(
      const std::string& currency_str,
      int64_t timestamp) = 0;
  virtual const ICurrency* GetCurrencyByNorm(
      const std::string& currency_str,
      int64_t timestamp) = 0;
};

template <class TProduct, class TCurrency>
class ProductInfoCache : public IProductInfoCache {
 public:
  ProductInfoCache() : symcache_{new SymbologyCache()} {}
  ProductInfoCache(const ProductInfoCache&) = delete;
  ProductInfoCache& operator=(const ProductInfoCache&) = delete;
  ~ProductInfoCache() = default;

  auto GetProductByNative(const std::string& symbol_str, int64_t timestamp)
      -> const IProduct* override {
    if (symcache_->has_product_native(symbol_str)) {
      return symcache_->GetProductByNative(symbol_str);
    }
    auto prod = TProduct::FromNativeStrNoThrow(symbol_str, timestamp);
    if (prod) {
      symcache_->AddProduct(std::move(prod));
      return symcache_->GetProductByNative(symbol_str);
    }
    return nullptr;
  }
  auto GetProductByNorm(const std::string& symbol_str, int64_t timestamp)
      -> const IProduct* override {
    if (symcache_->has_product_absolute_norm(symbol_str)) {
      return symcache_->GetProductByNorm(symbol_str);
    }
    auto prod = TProduct::FromNormStrNoThrow(symbol_str, timestamp);
    if (prod) {
      symcache_->AddProduct(std::move(prod));
      return symcache_->GetProductByNorm(symbol_str);
    }
    return nullptr;
  }
  auto GetCurrencyByNative(const std::string& currency_str, int64_t timestamp)
      -> const ICurrency* override {
    if (symcache_->has_currency_native(currency_str)) {
      return symcache_->GetCurrencyByNative(currency_str);
    }
    auto curr = TCurrency::FromNativeStrNoThrow(currency_str, timestamp);
    if (curr) {
      symcache_->AddCurrency(std::move(curr));
      return symcache_->GetCurrencyByNative(currency_str);
    }
    return nullptr;
  }

  auto GetCurrencyByNorm(const std::string& currency_str, int64_t timestamp)
      -> const ICurrency* override {
    if (symcache_->has_currency_norm(currency_str)) {
      return symcache_->GetCurrencyByNorm(currency_str);
    }
    auto curr = TCurrency::FromNormStrNoThrow(currency_str, timestamp);
    if (curr) {
      symcache_->AddCurrency(std::move(curr));
      return symcache_->GetCurrencyByNorm(currency_str);
    }
    return nullptr;
  }

 private:
  std::unique_ptr<SymbologyCache> symcache_;
};

}  // namespace coin2::exchange::base::order
